package viz_ui;

import java.awt.Color;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import controllers.ControlManager;

import buttons.Button;
import buttons.DoubleSlider;
import buttons.DropDownList;
import buttons.VerticalDoubleSlider;
import buttons.VerticalDropDownList;
import buttons.control.C_Button;
import buttons.control.C_DropDown;
import buttons.view.V_Button;
import buttons.view.V_DoubleSlider;
import buttons.view.V_DropDown;
import processing.core.PApplet;
import processing.core.PFont;
import utilities.EventListener;
import utilities.EventOwner;
import view.DisplayList;
import view.V_DisplayFeature;
import view.V_DisplayObject;
import viz_main.ScatterPlotAxis;
import viz_main.ScatterPlotCAxis;
import viz_main.ScatterPlotPoint;
import viz_main.VizApplet;

/***********************
*  The Grid view is a scaleable, adaptive view of a set of datapoints.
*  Using this approach allows multiple grid views on the same data and 
*  mapping values to different attributes.
*
*  UPDATE: Feb. 10:
*   The GridView now uses a ScatterPlotAxis class to keep track of axis data such as range and values to map.
*   The axis can be changed independantly through interacting with UI elements. When changes occur in either axis,
*   the GridView is notified and updates the view accordingly.
************************/

public class GridView extends V_DisplayObject implements EventListener{
  // DisplayObject has an x, y, xSize and ySize
  private VizApplet parent;
	
  private DropDownList xLabelList;
  private VerticalDropDownList yLabelList;
  private DoubleSlider xFilter;
  private VerticalDoubleSlider yFilter;
  private Button[] internalDisplayList;
  
  private ScatterPlotPoint[] sPoints = new ScatterPlotPoint[0];
  private ScatterPlotAxis xAxis;
  private ScatterPlotAxis yAxis;
  
  private float scale = 1.0f;
  private PFont labelFont; // used for text labels
  private PFont numFont; // used for numbers
  private int minFntSz = 8;
  private int maxFntSz = 12;
  private int curFntSz;
  
  private int gridIndex = 0;
  private int curSelectedIndex = -1;
  
  // most important values for the grid view are the x and y position and the width and height
  public GridView(float xPos, float yPos, float w, float h, ControlManager c, VizApplet p)
  {
	this.init(xPos, yPos, w, h, new Color(230, 230, 230, 150));
    parent = p;
    // NOTE: this component manages the display of its internal buttons but it sends the update
    // management for interaction to the controlManager.
    
    curFntSz = (minFntSz+maxFntSz)/2;
    labelFont = p.createFont("Arial-Black-12", curFntSz);
    numFont = p.createFont("ArialMT-12", minFntSz);
   
    //(ScatterPlotAxis)axisList.get(labels[0])
    xLabelList = new DropDownList(null, c, "xAxis", 85, 12, parent);
    xLabelList.addOptions(p.labels);
    xLabelList.addSubscriber(this);
    xLabelList.setNameVisible(false);
    
    yLabelList = new VerticalDropDownList(null, c, "yAxis", 12, 85, parent);
    yLabelList.addOptions(p.labels);
    yLabelList.addSubscriber(this);
    yLabelList.setNameVisible(false);
    
    xFilter = new DoubleSlider(null, c, "xFilter", w/4, 12);
    xFilter.addSubscriber(this);
    yFilter = new VerticalDoubleSlider(null, c, "yFilter", 12.0f, h/4);
    yFilter.addSubscriber(this);
    
    internalDisplayList = new Button[]{xLabelList, yLabelList, xFilter, yFilter};
  }
  
  public void setXAxis(ScatterPlotAxis axis)
  {
    xAxis = axis;
    ((V_DropDown)xLabelList.getView()).setCurOption(xAxis.getLabel());
    ((C_DropDown)xLabelList.getController()).cacheCurOption();
    xAxis.setFilteredX(xFilter.getXPercent());
    xAxis.setFilteredY(xFilter.getYPercent());
  }
  
  public void setYAxis(ScatterPlotAxis axis)
  {
    yAxis = axis;
    ((V_DropDown)yLabelList.getView()).setCurOption(yAxis.getLabel());
    ((C_DropDown)yLabelList.getController()).cacheCurOption();
    yAxis.setFilteredX(yFilter.getXPercent());
    yAxis.setFilteredY(yFilter.getYPercent());
  }
  
  // Req. non-null axis in order to properly generate the points:
  public void generatePoints(int number)
  {
    sPoints = new ScatterPlotPoint[number];
    if(xAxis != null && yAxis != null){
      for(int i = 0; i < number; i++)
      {
        sPoints[i] = new ScatterPlotPoint(xAxis.getElement(i), yAxis.getElement(i), 5.0f, parent.pointColor);
        // scale: ((maxS-minS)/maxSz)*sz[i-1]
      }
    }
  }
  // Req. non-null axis and an already existing array of points:
  public void updatePoints()
  {
    if(sPoints != null){
      for(int i = 0; i < sPoints.length; i++)
      {
        sPoints[i].setXLoc(xAxis.getElement(i));
        sPoints[i].setYLoc(yAxis.getElement(i));
      }
    }
  }
  
  @Override
  public void display(PApplet p) {
    p.noStroke();
    p.fill(bgColor);
    p.rect(xLoc, yLoc, xSize, ySize);
    p. fill(0);
    
    // draw lines and numbers:
    p.textFont(numFont);
    p.stroke(100);
    p.strokeWeight(1);
    float p1 = 1; // a pad value (adjust to increase/decrease tightness to the sides
    float ip = 30; // provide more indentation space when the numbers are larger
    float yIp = 14;
    float yStart = yLoc + ySize - curFntSz - p1 - yIp;
    float yEnd = yLoc + p1 + 4; 
    float xStart = xLoc+p1+ip+curFntSz;
    float xEnd = xLoc+xSize-8;
    
    p.line(xStart, yStart, xStart, yEnd); // draw vertical line
    p.line(xStart, yStart, xEnd, yStart); // draw horizontal line
    
    int rnd = 100; // round values to hundredths.
    // based on the width of the GridView and the range of values, calculate distribution:
    float t = 1.5f; // half the length of the tick line segment
    
    // draw ticks for x-Axis:
    //System.out.println(xAxis.getLabel());
    //System.out.println((xAxis.getMax() - xAxis.getMin())/xAxis.getTicks());
    float inc = (xAxis.getMax() - xAxis.getMin())/xAxis.getTicks(); // the data interval (for text display)
    float xInterval = ((xEnd) - xStart)/xAxis.getTicks(); // the actual interval
    float curXPos = xStart;
    float curInc = xAxis.getMin(); // TODO: set to zero later
    
    p.text((float)((int)(curInc*10))/10+"", curXPos-(p.textWidth((float)((int)(curInc*10))/10+"")/2), yStart + t + minFntSz);
    // NOTE: use the same loop rather than a different loop for each axis!
    for(int i = 0; i < xAxis.getTicks(); i++) {
      float tmpXPos = curXPos;
      for(int j = 0; j < xAxis.getSubTicks(); j++) // draw 3 sub-ticks:
      {
        tmpXPos += xInterval/xAxis.getSubTicks();
        p.stroke(210);
        p.line(tmpXPos, yStart, tmpXPos, yEnd);
        p.stroke(0);
        p.line(tmpXPos, yStart + (t), tmpXPos, yStart - (t));
      }
     
      p.strokeWeight(2);
      curXPos += xInterval;
      p.line(curXPos, yStart + t, curXPos, yStart - t);
      p.strokeWeight(1);
      curInc += inc;
      // TODO: use getMark();
      p.text((float)((int)(curInc*rnd))/rnd+"", curXPos-(p.textWidth((float)((int)(curInc*rnd))/rnd+"")/2), yStart + t + minFntSz);
    }
    
    // draw ticks for y-Axis:
    inc = (yAxis.getMax() - yAxis.getMin())/yAxis.getTicks(); // the data interval (for text display)
    float yInterval = ((yStart) - yEnd)/yAxis.getTicks();  // the actual interval
    float curYPos = yStart;
    curInc = yAxis.getMin()*-1;
    
    p.text(( (float)((int)(curInc*10))/10)*-1+"", xStart-(p.textWidth(( (float)((int)(curInc*10))/10)*-1+""))-t-3, curYPos + minFntSz/2);
    for(int i = 0; i < yAxis.getTicks(); i++) {
      float tmpYPos = curYPos;
      for(int j = 0; j < yAxis.getSubTicks(); j++) // draw 3 sub-ticks:
      {
        tmpYPos -= yInterval/yAxis.getSubTicks();
        p.stroke(210);
        p.line(xStart, tmpYPos, xEnd, tmpYPos);
        p.stroke(0);
        p.line(xStart - t, tmpYPos, xStart + t, tmpYPos);
      }
      p.strokeWeight(2);
      curYPos -= yInterval;
      p.line(xStart - t, curYPos, xStart + t, curYPos);
      p.strokeWeight(1);
      curInc -= inc;
      p.text(( (float)((int)(curInc*rnd))/rnd)*-1+"", xStart-(p.textWidth(( (float)((int)(curInc*rnd))/rnd)*-1+""))-t-3, curYPos + minFntSz/2);
    }
    
    // draw labels:    
    yLabelList.getView().setLocation(xLoc + p1, yLoc+(ySize/2) - yLabelList.getView().getYSize()/2);
    yFilter.getView().setLocation(xLoc + p1, yEnd);
    xLabelList.getView().setLocation(xLoc + (xSize/2) - (xLabelList.getView().getXSize()/2), yLoc + ySize - xLabelList.getView().getYSize()-p1);
    xFilter.getView().setLocation(xEnd-xFilter.getView().getXSize(), yLoc + ySize - xLabelList.getView().getYSize()-p1);
    
    // draw points:
    for(int i = 0; i < sPoints.length; i++)
    {
    	this.drawPoint(i, xStart, xEnd, yStart, yEnd);
    }
    if(curSelectedIndex > -1)
    {
    	this.drawPoint(curSelectedIndex, xStart, xEnd, yStart, yEnd);
    }
    // draw buttons:
    for(int i = 0; i < internalDisplayList.length; i++)
    {
    	internalDisplayList[i].display(p);
    }
  }
  
  public void drawPoint(int i, float xStart, float xEnd, float yStart, float yEnd)
  {
	  float xPercent = (sPoints[i].getXLoc() - xAxis.getMin())/(xAxis.getMax() - xAxis.getMin());
      float yPercent = (sPoints[i].getYLoc() - yAxis.getMin())/(yAxis.getMax() - yAxis.getMin());
      // any value greater than the total percentage will be displayed at the edge:
      float localX = ((xEnd - xStart)*Math.min(1.0f, Math.max(0.0f, xPercent) ));
      float localY = ((yStart - yEnd)*Math.max(0.0f, Math.min(yPercent, 1.0f) )); // note: y increases inversely
      sPoints[i].display(xStart + localX, yStart - localY, parent);
  }
  
  // ALLOW FOR POINT SELECTION:
  public void onInteract(float mouseXLoc, float mouseYLoc, String interID)
  {
	  if(this.contains(mouseXLoc, mouseYLoc)) // if within the bounds of this grid view...
	  {
		  for(int i = 0; i < sPoints.length; i++) // iterate through each point
	      {
			  if(sPoints[i].checkOver(mouseXLoc, mouseYLoc)) // compare its position with the mouse position
			  {
				  parent.cursor(PApplet.HAND);
				  
				  if(interID.equals("leftClick"))
				  {
					  if(curSelectedIndex == i) // toggle the current selection
					  {
						  sPoints[i].setSelected(false);
						  parent.deselectElement();
						  curSelectedIndex = -1;
					  }
					  else 
					  {
						  this.deselectPoint();
						  sPoints[i].setSelected(true);
						  parent.selectAnElement(i);
						  curSelectedIndex = i;
					  }
				  }
				  
				  return;
			  }
	      }
		  parent.cursor(PApplet.ARROW);	
	  }
  }
  
  // Dynamic Attributes:
  // allow for setting the size, opacity, color, and shape dynamically:
  public void setSizeToAxis(ScatterPlotAxis axis)
  {
    if(axis.getType() == 1)
    {
      ScatterPlotCAxis cAxis = (ScatterPlotCAxis)axis;
      HashMap uHM = cAxis.getCategoryMap();
      Iterator i = uHM.entrySet().iterator();  // Get an iterator
      int count = 0;
      while (i.hasNext()){
        Map.Entry me = (Map.Entry)i.next();
        int[] curIndexList = (int[])me.getValue();
        for(int j = 0; j < curIndexList.length; j++)
        {
          sPoints[curIndexList[j]].setSize(PApplet.map(count, 0, uHM.size(), 3.0f, 12.0f));
        }
        count++;
      }
    }
    else if(axis.getType() == 0)
    {
      for(int i = 0; i < axis.getLength(); i++)
      {
        sPoints[i].setSize( PApplet.map(axis.getElement(i), axis.getMin(), axis.getMax(), 3.0f, 12.0f) );
      }
    }
  }
  public void clearSize()
  {
    for(int i = 0; i < sPoints.length; i++)
    {
      sPoints[i].setSize(parent.baseS); // use a global variable to reset size
    }
  }
  
  public void setColors(ScatterPlotAxis axis)
  {
    if(axis.getType() == 1)
    {
      ScatterPlotCAxis cAxis = (ScatterPlotCAxis)axis;
      HashMap uHM = cAxis.getCategoryMap();
      Iterator i = uHM.entrySet().iterator();  // Get an iterator
      int count = 0;
      while (i.hasNext()){
        Map.Entry me = (Map.Entry)i.next();
        int[] curIndexList = (int[])me.getValue();
        for(int j = 0; j < curIndexList.length; j++)
        {
          sPoints[curIndexList[j]].setBGColor(parent.colors[count]); // refer to the global list of colors
        }
        count++;
      }
    }
    else if(axis.getType() == 0)
    {
      int[] gradient = parent.getGradient();
      for(int i = 0; i < sPoints.length; i++)
      {
        float percentage = (axis.getElement(i) - axis.getMin())/(axis.getMax() - axis.getMin());
        int value = (int)((gradient.length-1)*percentage);
        // map betwen a gradient of 2 colors:
        sPoints[i].setBGColor(gradient[value]);
      }
    }
  }
  public void clearColors()
  {
    for(int i = 0; i < sPoints.length; i++)
    {
      sPoints[i].setBGColor(parent.pointColor); // use a global variable to reset size
    }
  } 
  
  public void setOpacity(ScatterPlotAxis axis)
  {
    if(axis.getType() == 1)
    {
      ScatterPlotCAxis cAxis = (ScatterPlotCAxis)axis;
      HashMap uHM = cAxis.getCategoryMap();
      Iterator i = uHM.entrySet().iterator();  // Get an iterator
      int count = 0;
      while (i.hasNext()){
        Map.Entry me = (Map.Entry)i.next();
        int[] curIndexList = (int[])me.getValue();
        for(int j = 0; j < curIndexList.length; j++)
        {
          sPoints[curIndexList[j]].setOpacity(PApplet.map(count, 0, uHM.size(), 0.1f, 1.0f));
        }
        count++;
      }
    }
    else if(axis.getType() == 0)
    {
      for(int i = 0; i < axis.getLength(); i++)
      {
        sPoints[i].setOpacity( Math.min(0.05f + (axis.getElement(i) - axis.getMin())/(axis.getMax() - axis.getMin()), 1.0f) );
      }
    }
  }
  public void clearOpacity()
  {
    for(int i = 0; i < sPoints.length; i++)
    {
      sPoints[i].setOpacity(1.0f); // use a global variable to reset size
    }
  }
  @Override
  public void handleEvent(String trigger, EventOwner obj)
  {
	  HashMap[] axisList = parent.getAxisList();
      String buttonName = ( (V_Button)((C_Button)obj).getView() ).getName();
      if(buttonName.equals("xAxis"))
      {
        this.setXAxis((ScatterPlotAxis)axisList[gridIndex].get(trigger));
        updatePoints();
      }
      else if(buttonName.equals("yAxis"))
      {
        this.setYAxis((ScatterPlotAxis)axisList[gridIndex].get(trigger));
        updatePoints();
      }
      else if(buttonName.equals("xFilter"))
      {
        V_DoubleSlider tmpButton = (V_DoubleSlider)((C_Button)obj).getView();
        if(trigger.equals("moveLeftSlider")) {
          xAxis.setFilteredX(tmpButton.getXPercent());
        }
        else if(trigger.equals("moveRightSlider")) {
          xAxis.setFilteredY(tmpButton.getYPercent());
        }
        else if(trigger.equals("moveBothSliders")) {
          xAxis.setFilteredX(tmpButton.getXPercent());
          xAxis.setFilteredY(tmpButton.getYPercent());
        }
      }
      else if(buttonName.equals("yFilter"))
      {
        V_DoubleSlider tmpButton = (V_DoubleSlider)((C_Button)obj).getView();
        if(trigger.equals("moveLeftSlider")) {
          yAxis.setFilteredX(tmpButton.getXPercent());
        }
        else if(trigger.equals("moveRightSlider")) {
          yAxis.setFilteredY(tmpButton.getYPercent());
        }
        else if(trigger.equals("moveBothSliders")) {
          yAxis.setFilteredX(tmpButton.getXPercent());
          yAxis.setFilteredY(tmpButton.getYPercent());
        }
      }
  }
  
  // Tell the grid to store what index it is in the list of gridViews
  public void setIndex(int val)
  {
    gridIndex = val;
  }

  public void deselectPoint() {
	  if(curSelectedIndex > -1) // turn off the previous selection:
	  {
		  sPoints[curSelectedIndex].setSelected(false);
		  curSelectedIndex = -1;
	  }
  }
}
